package cn.iocoder.yudao.module.system.service.common;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import org.apache.commons.lang3.StringUtils;

import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class UdpReflectUtil {

    public static final String[] COPY_IGNORE_PROPERTIES = {};

    private UdpReflectUtil() {

    }

    public static String getPropertyByNameIgnoreCase(String propertyName, Class<?> clazz) {
        PropertyDescriptor property = BeanUtil.getPropertyDescriptor(clazz, propertyName, true);
        return property == null ? null : property.getName();
    }

    public static Class<?> getPropertyTypeByNameIgnoreCase(String propertyName, Class<?> clazz) {
        PropertyDescriptor property = BeanUtil.getPropertyDescriptor(clazz, propertyName, true);
        return property == null ? null : property.getPropertyType();
    }

    public static List<String> getPropertyNames(Class<?> clazz) {
        Map<String, PropertyDescriptor> propertyDescriptorMap = BeanUtil.getPropertyDescriptorMap(clazz, false);
        return new ArrayList<>(propertyDescriptorMap.keySet());
    }

    public static <T> T entityCopy(T t, Class<T> clazz) {
        T newObj = ReflectUtil.newInstance(clazz);
        BeanUtil.copyProperties(t, newObj, COPY_IGNORE_PROPERTIES);
        return newObj;
    }

    @SuppressWarnings("unchecked")
    public static <T> T entityCopy(T t) {
        Assert.notNull(t, "cat not copy null object");
        return entityCopy(t, (Class<T>) t.getClass());
    }

    public static <T> List<T> entitiesCopy(List<T> entities, Class<T> clazz) {
        List<T> results = new LinkedList<>();
        for (T t : entities) {
            T newObj = ReflectUtil.newInstance(clazz);
            BeanUtil.copyProperties(t, newObj, COPY_IGNORE_PROPERTIES);
            results.add(newObj);
        }
        return results;
    }

    public static <T> List<T> entitiesCopy(List<T> entities) {
        return entitiesCopy(entities, (Class<T>) entities.get(0).getClass());
    }

    public static Object getFieldValue(Object bean, String filedName) {
        return BeanUtil.getFieldValue(bean, filedName);
    }

    public static void setFieldValue(Object bean, String propertyName, Object value) {
        BeanUtil.setFieldValue(bean, propertyName, value);
    }

    public static Object getFieldValueIgnoreCase(Object bean, String propertyName) {
        String fieldName = getPropertyByNameIgnoreCase(propertyName, bean.getClass());
        if (StringUtils.isBlank(fieldName)) {
            return null;
        }
        return getFieldValue(bean, fieldName);
    }

    public static void setFieldIgnoreCase(Object entity, String propertyName, Object value) {
        String pname = getPropertyByNameIgnoreCase(propertyName, entity.getClass());
        if (StringUtils.isNotBlank(pname)) {
            BeanUtil.setFieldValue(entity, pname, value);
        }
    }

    public static String getFiledNameByAlias(Class<?> beanClass, String name) throws SecurityException {
        String fieldName = getPropertyByNameIgnoreCase(name, beanClass);
        if (StringUtils.isNotBlank(fieldName)) {
            return fieldName;
        }
        if (name.contains("_")) {
            String tempStr = name.replace("_", "");
            if (StringUtils.isBlank(tempStr)) {
                return null;
            }
            return getPropertyByNameIgnoreCase(tempStr, beanClass);
        }
        return null;
    }

    public static Field getField(Class<?> beanClass, String name) {
        Field field = ReflectUtil.getField(beanClass, name);
        if (field != null) {
            return field;
        }
        Field[] fields = ReflectUtil.getFields(beanClass);
        return Stream.of(fields).filter(i -> StrUtil.equalsIgnoreCase(name, i.getName())).findAny().orElse(null);
    }

    public static List<Field> getFieldsByAnnotation(Class<?> beanClass, Class<? extends Annotation> annotationClass) {
        Field[] fields = ReflectUtil.getFields(beanClass);
        return Arrays.stream(fields).filter(f -> f.isAnnotationPresent(annotationClass)).collect(Collectors.toList());
    }

    public static Field getFieldByIgnoreCaseName(Class<?> clazz, String name) {
        Field f = getField(clazz, name);
        if (f != null) {
            return f;
        }
        final Field[] fields = ReflectUtil.getFields(clazz);
        if (ArrayUtil.isNotEmpty(fields)) {
            for (Field field : fields) {
                if ((name.equalsIgnoreCase(field.getName()))) {
                    return field;
                }
            }
        }
        return null;
    }

    /**
     * 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处
     * 如无法找到, 返回Object.class.
     * eg.
     * public UserDao extends HibernateDao<User>
     *
     * @param clazz The class to introspect
     * @return the first generic declaration, or Object.class if cannot be determined
     */
    public static Class<?> getClassGenericType(final Class<?> clazz) {
        return getClassGenericType(clazz, 0);
    }

    /**
     * 通过反射, 获得Class定义中声明的父类的泛型参数的类型.
     * 如无法找到, 返回Object.class.
     * <p>
     * 如public UserDao extends HibernateDao<User,Long>
     *
     * @param clazz clazz The class to introspect
     * @param index the Index of the generic ddeclaration,start from 0.
     * @return the index generic declaration, or Object.class if cannot be determined
     */
    public static Class getClassGenericType(final Class clazz, final int index) {
        Type genType = clazz.getGenericSuperclass();
        if (!(genType instanceof ParameterizedType)) {
            return Object.class;
        }
        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();

        if (index >= params.length || index < 0) {
            return Object.class;
        }
        if (!(params[index] instanceof Class)) {
            return Object.class;
        }
        return (Class) params[index];

//        ResolvableType resolvableType = ResolvableType.forClass(clazz);
//        ResolvableType superType = resolvableType.getSuperType();
//        System.out.println(superType.getGeneric(0).resolve());
    }
}
